/**
 * @file Shell_servidor.c
 * @brief Programa servidor
 *
 * Fichero que contiene el programa servidor de shell remota
 * El mismo al ejecutarse permanece a la espera de clientes. Cuando uno se 
 * conecta, procede a recibir peticiones, ejecutarlas y devolver su respuesta.
 */
#include <sys/wait.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "shell_protocols.h"
#include "nivel2.h"

/**
 * Tamaño por defecto del bloque de datos que envia el servidor
 */
#define DEFAULT_BLOCK_LEN 64

/**
 * Flag que señala la presencia de una solicitud de desconexion por parte
 * del usuario
 */
int quit_request = 0;

/**
 * Flag que señala la existencia de una conexion con un usuario 
 */
int connection_established = 0;

/**
 * Longitud de los bloques de datos que envia el servidor
 */
int block_len = 0;


/**void print_mac(BYTE* mac) {
  printf("%X:%X:%X:%X:%X:%X", mac[0], mac[1], mac[2],mac[3], mac[4],mac[5]);
  }*/

/**
 * @brief Funcion de finalizacion del servidor
 *
 * La funcion server_shutdown finaliza el programa servidor,
 * desconectandose de un cliente si estuviera conectado y cerrando la suite
 * de protocolos de nivel 2
 *
 * Siempre se llama antes del cerrado del programa por cualquier motivo
 */
void server_shutdown() {
  int ret;
  if(connection_established) {
    connection_established = 0;
    printf("Forzando cierre de conexion\n");
    Desconectar();
  }
  printf("Finalizando protocolos de nivel 2\n");
  ret = FinalizarNivel2();
  if(ret == ETH_OK)
    printf("Protocolos de nivel 2 finalizados\n");
  else
    printf("Error al finalizar protocolos de nivel 2\nFin de la ejecucion\n\n");
  
  /* Referencia a 2010, la secuela de 2001: Una odisea en el espacio */
  /* Esperamos que un detalle tan inocuo no repercuta negativamente en la calificacion */
  printf("+===============================================+\n");
  printf("|All intelligent beings dream. Nobody knows why.|\n");
  printf("+===============================================+\n");
}

/**
 * @brief Funcion de captura de interrupciones
 *
 * La funcion sig_handler es el manejador de interrupciones del programa,
 * que se llama cuando se recibe sigint para iniciar un protocolo de cerrado 
 * correcto
 *
 * @param signum Numero de la señal recibida
 */
void sig_handler(int signum) {
  if(signum == SIGINT) {
    printf("Terminacion iniciada por el usuario. Iniciando secuencia de apagado\n");
    server_shutdown();
    exit(0);
  } 
}

/**
 * @brief Funcion auxiliar de envio de mensaje OK
 *
 * @return ETH_OK en caso de envio correcto, ETH_ERROR en caso contrario
 */
int reply_ok() {
  BYTE message;
  message = REP_OK;
  return EnviarDatos(&message, 1);
}

/**
 * @brief Funcion auxiliar de envio de mensaje ERROR
 *
 * @return ETH_OK en caso de envio correcto, ETH_ERROR en caso contrario
 */
int reply_error() {
  BYTE message;
  message = REP_ERR;
  return EnviarDatos(&message, 1);
}

/**
 * @brief Funcion auxiliar de envio de mensaje DATA
 *
 * @param data_size Tamaño del tren de datos que se va a enviar a continuacion
 * @return ETH_OK en caso de envio correcto, ETH_ERROR en caso contrario
 */
int reply_data(uint32_t data_size) {
  BYTE message[1 + sizeof(uint32_t)];
  message[0] = REP_DATA;
  memcpy(message + 1,(BYTE*) &data_size, sizeof(uint32_t));
  return EnviarDatos(message, 1 + sizeof(uint32_t));
}

/**
 * @brief Funcion de notificacion de nivel 2
 * 
 * La funcion de notificacion acepta todos los mensajes que consistan en
 * una peticion bien formulada, o bien un mensaje de desconexion.
 * En caso de encontrar un mensaje de otro tipo, responde con REP_ERR
 *
 * @param msg_size Tamaño del mensaje recibido en bytes
 * @param msg Puntero al mensaje recibido
 * @return ETH_OK en caso de aceptar el mensaje, ETH_ERR en caso contrario
 */
int callback(int msg_size, const BYTE * msg) {
  if(msg_size == 0 ) {
    quit_request = 1;
    return ETH_OK;
  } else if(msg_size < sizeof(Request_Type)) {
    reply_error();
    return ETH_ERROR;
  } else {
    /* Comprobacion de tipo del mensaje */
    switch(*((Request_Type *) msg)) {
    case REQ_CD:
    case REQ_RUN:
      /* Run o CD no pueden no tener argumentos */
      if(msg_size - sizeof(Request_Type) <= 0) {
	reply_error();
	return ETH_ERROR;
      }
      return ETH_OK;
      break;
    case REQ_LS:
    case REQ_PWD:
      /* LS o PWD no pueden tenerlos */
      if(msg_size != sizeof(Request_Type)) {
	reply_error();
	return ETH_ERROR;
      }
      return ETH_OK;
      break;
    default:
      reply_error();
      return ETH_ERROR;
    }	 
  }
}

/**
 * @brief Funcion auxiliar que interpreta un array de bytes como un mensaje (asumiendo que tal interpretacion pueda hacerse)
 * 
 * @param msg Puntero al array de bytes que contiene el mensaje
 * @param req Puntero a la estructura Request a llenar
 */
void message_as_request(BYTE * msg, Request * req) {
  req->type = *((Request_Type*)msg);
  switch(req->type) {
  case REQ_CD:
  case REQ_RUN:
    req->parameters =(char*) &(msg[sizeof(Request_Type)]);
    break;
  case REQ_LS:
  case REQ_PWD:
    req->parameters = NULL;
    break;
  default:
    return;
  }
}

/**
 * @brief Funcion de envio de bloque de datos
 *
 * La funcion send_block_data vuelca sobre una conexion
 * todos los datos del fichero especificado en bloques de tamaño
 * COMO MAXIMO el especificado por el usuario (o el tamaño por defecto)
 *
 * @param f Fichero a volcar
 * @param size Tamaño del fichero
 * @return ETH_OK en caso de envio correcto de todos los bloques, ETH_ERROR en caso contrario
 */
#define BLOCK_LEN ((block_len <= 0)?DEFAULT_BLOCK_LEN:block_len)
int send_block_data(FILE * f, size_t size) {
  int sent = 0;
  BYTE send_buffer[256];
  int i;
  int ret = ETH_OK;
  int file_end = 0;
  char c;

  /* Bucle externo: una iteracion por bloque */
  while(ret == ETH_OK && sent != size && !file_end) {
    i = 0;
    /* Bucle interno: una iteracion por caracter */
    while(i != BLOCK_LEN && !file_end) {
      c = fgetc(f);
      if(c == EOF)
	file_end = 1;
      else {
	send_buffer[i] = c;
	i++;
      }
	
    }
    ret = EnviarDatos(send_buffer, i - file_end);
    sent = sent + i - file_end;
  }
  return ret;
}

/**
 * @brief Funcion de envio en bloques de cadenas de texto
 *
 * La funcion send_block_string envia en bloques (de manera similar a send_block_data)
 * el contenido de una cadena de texto especificada
 *
 * @param str Puntero a la cadena de texto a enviar
 * @return ETH_OK en caso de envio correcto de cada bloque, ETH_ERROR en caso contrario
 */ 
int send_block_string(char * str) {
  BYTE send_buffer[256];
  int i;
  int j = 0;
  int ret = ETH_OK;
  int length = strlen(str);

  /* Bucle externo: una iteracion por cada bloque enviado */
  while(j != length && ret == ETH_OK) {
    i = 0;
    /* Bucle interno: una iteracion por cada caracter de la cadena */
    while(i < BLOCK_LEN && j != length) {
      send_buffer[i] = str[j];
      i++; j++;
    }
    ret = EnviarDatos(send_buffer, i);
  }
  return ret;
}
  
#undef BLOCK_LEN

/**
 * @brief Funcion que ejecuta un comando shell redirigiendo su salida a un fichero
 *
 * La funcion run_piped ejecuta -a traves de una llamada a system()- el comando
 * especificado, y redirigiendo su salida estandar al fichero pipe en el directorio
 * home del usuario, devolviendo el mismo abierto para su lectura
 *
 * @param command Cadena de texto con el comando que sera ejecutado
 * @return Puntero al fichero que contiene la salida del programa (o NULL en caso de error)
 */
FILE * run_piped(char * command) {
  FILE * pipe;
  char * cmd;
  int ret;

  /* Composicion del comando de redireccion */
  cmd = calloc(strlen(command) + strlen(" > ~/pipe") + 1, sizeof(char));
  if(!cmd)
    return NULL;

  strcpy(cmd, command);
  strcat(cmd, " > ~/pipe");
  printf("%s\n", cmd);

  ret = system(cmd);
  free(cmd);

  /* Espera al proceso hijo generado por system() */
  wait(NULL);

  /* Devolver NULL en caso de error */
  if(WEXITSTATUS(ret) != 0)
    return NULL;

  /* Composicion de la ruta del fichero de redireccion */
  cmd = calloc(strlen(getenv("HOME")) + strlen("/pipe") + 1, sizeof(1));
  if(!cmd)
    return NULL;

  strcpy(cmd, getenv("HOME"));
  strcat(cmd, "/pipe");
  pipe = fopen(cmd, "r+");
  free(cmd);

  return pipe;
}

/**
 * @brief Funcion auxiliar que calcula el tamaño de un archivo
 * @param f Archivo examinado
 * @return Longitud del archivo sin contar el EOF
 */
int file_size(FILE * f) {
  int ret;
  fseek(f, 0L, SEEK_END);
  ret = ftell(f);
  fseek(f, 0L, SEEK_SET);
  return ret - 1;
}

/**
 * @brief Funcion de procesado de peticiones
 *
 * La funcion handle_request toma una peticion, la ejecuta, y envia
 * al cliente un mensaje con la informacion pertinente: OK, ERROR o bloques
 * de datos con la salida del comando ejecutado
 *
 * @param req Puntero a la peticion a ejecutar 
 * @return ETH_OK en caso de responder correctamente al cliente, ETH_ERROR en caso de que algun envio de informacion falle
 */
int handle_request(Request * req) {
  int ret;
  FILE * file_handler;
  char * wd;

  switch(req->type) {
    /* Manejo de peticiones CD */
  case REQ_CD:
    ret = chdir(req->parameters);
    if(ret == 0) {
      return reply_ok();
    } else {
      return reply_error();
    }
    break;

    /* Manejo de peticiones LS */
  case REQ_LS:
    /* Ejecucion shell de comando ls */
    file_handler = run_piped("ls");
    if(file_handler == NULL) {
      return reply_error();
    }
    
    if(file_size(file_handler) <= 0) {
      ret = reply_ok(); /* Si no se han generado datos (i.e. directorio vacio) se devuelve OK */
    } else {
      /* Envio de mensaje DATA: informa al cliente de que espere un bloque de datos */
      if((ret = reply_data(file_size(file_handler))) != ETH_OK) {
	fclose(file_handler);
	return ret;
      }
      /* Envio de bloques de datos */
      ret = send_block_data(file_handler, file_size(file_handler));
      fclose(file_handler);
    }
    return ret;
    break;

    /* Manejo de peticiones PWD */
  case REQ_PWD:
    /* Lectura del CWD */
    wd = getcwd(NULL, 0);
    if(wd == NULL)
      return reply_error();

    /* Envio de mensaje DATA */
    if((ret = reply_data(strlen(wd))) != ETH_OK) {
      free(wd);
      return ret;
    }
    
    /* Envio de bloques de datos */
    ret = send_block_string(wd);
    free(wd);
    return ret;

    /* Manejo de peticiones RUN */
  case REQ_RUN:
    /* Ejecucion del comando */
    file_handler = run_piped(req->parameters);
    if(file_handler == NULL) {
      return reply_error();
    }
    
    /* Si el comando no genera salida, se devuelve un mensaje OK */
    if(file_size(file_handler) <= 0) {
      ret = reply_ok();
    } else {
      /* Envio de mensaje DATA */
      if((ret = reply_data(file_size(file_handler))) != ETH_OK) {
	fclose(file_handler);
	return ret;
      }
      /* Envio de bloques de datos */
      ret = send_block_data(file_handler, file_size(file_handler));
      fclose(file_handler);
    }
    return ret;
    break;

  default:
    return ETH_ERROR;
  }
}

/**
 * @brief Programa principal
 */
int main(int argc, char ** argv) {
  int quit_request = 0; /* Flag de desconexion del cliente */
  int result; /* Variable para almacenar resultados de operaciones de envio/recepcion */
  Request request; /* Variable para almacenar el request recibido */
  BYTE dir_cliente[6]; /* Direccion del cliente (broadcast) */
  int message_size; /* Tamaño del mensaje a enviar */
  BYTE data[256]; /* Buffer de entrada de mensajes */
  char * check_ptr; /* Puntero para comprobacion de argumentos de inicio */

  /* Lectura del tamaño de bloque de los argumentos */
  if(argc < 2) {
    printf("No se ha especificado una longitud de bloque de envio\n");
    printf("El programa usara una longitud por defecto de 64 bytes\n");
  } else if(argc == 2) {
    block_len = strtol(argv[1], &check_ptr, 10);
    if(check_ptr == argv[1] || block_len > 255) {
      printf("La longitud de bloque de envio especificada es invalida\n");
      printf("El programa usara una longitud por defecto de 64 bytes\n");
      printf("La longitud de bloque maxima es de 255 bytes\n");
      block_len = 0;
    } else {
      printf("Servidor configurado con una longitud de bloque de %d bytes\n", block_len);
    }
  } else {
    printf("Demasiados argumentos\n");
    printf("Ejecutar con %s [long_bloque]\n", argv[0]);
    exit(1);
  }
      

  dir_cliente[0]=dir_cliente[1]=dir_cliente[2]=
    dir_cliente[3]=dir_cliente[4]=dir_cliente[5]=0xFF;

  /* Inicializacion del nivel 2 */
  printf("Iniciando nivel 2\n");
  if(IniciarNivel2(callback) == ETH_ERROR) {
    printf("Imposible iniciar nivel 2\n");
    return -1;
  }
  printf("Nivel 2 iniciado\n\n");

  /* Captura de señales */
  printf("Capturando señales\n");
  signal(SIGINT, sig_handler);
  printf("Señal de interrupcion capturada correctamente\n\n");

  /* Bucle principal */
  while(1) {
    chdir(getenv("HOME")); /* El programa se inicia en el directorio home del usuario */
    printf("Esperando conexiones\n");
    quit_request = 0;
    connection_established = 0;

    /* Espera de conexion */
    result = AceptarConexion(dir_cliente, 0);
    
    if(result != ETH_OK) {
      printf("Error al intentar aceptar conexiones\n");
    } else {
      printf("Conexion recibida");
      printf("\nEsperando comandos...\n");
      connection_established = 1; 
      
      while(!quit_request) {
	/* Recepcion de mensaje (que es peticion valida o hubiera sido filtrado por callback */
	message_size = RecibirDatos(data, 0);
	switch(message_size) {
	case 0: /* Solicitud de desconexion detectada */
	  quit_request = 1;
	  printf("Solicitud de desconexion recibida del cliente\n");
	  break;
	case ETH_ERROR: /* Error al recibir */
	  printf("Error al recibir peticion del cliente\n");
	  break;
	case ETH_TIMEOUT: /* Tiempo de espera agotado -imposible, timeout 0- */
	  printf("Tiempo de espera agotado\n");
	  break;
	default: /* Peticion correcta */
	  message_as_request(data, &request);
	  /* Procesado de peticion. Un error en handle_request denota un error en el envio
	     de datos y, por lo tanto, de un corte en la conexion */
	  if(handle_request(&request) == ETH_ERROR) {
	    printf("Error al enviar datos al cliente\n");
	    printf("La conexion ha sido cortada\n");
	    quit_request = 1;
	  }
	}
      }
    
      printf("Procediendo a desconectar\n");
    }
  }
  return 0;
}
